package com.dlc.shop.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.dlc.shop.bean.app.dto.ShopCartItemDiscountDto;
import com.dlc.shop.bean.app.dto.ShopCartItemDto;
import com.dlc.shop.bean.app.dto.ShopCartOrderDto;
import com.dlc.shop.bean.enums.*;
import com.dlc.shop.bean.event.EsProductUpdateEvent;
import com.dlc.shop.bean.model.Order;
import com.dlc.shop.bean.model.OrderItem;
import com.dlc.shop.bean.model.Product;
import com.dlc.shop.bean.model.Sku;
import com.dlc.shop.bean.vo.SkuStockVO;
import com.dlc.shop.bean.vo.WarehouseVO;
import com.dlc.shop.common.config.Constant;
import com.dlc.shop.common.constants.StockPointType;
import com.dlc.shop.common.exception.YamiShopBindException;
import com.dlc.shop.common.response.ResponseEnum;
import com.dlc.shop.common.response.ServerResponseEntity;
import com.dlc.shop.manager.impl.StockManager;
import com.dlc.shop.service.*;
import com.dlc.shop.util.StockUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 库存锁定信息
 *
 * @author lhd
 * @date 2022-05-06 17:27:53
 */
@Slf4j
@Service
@AllArgsConstructor
public class SkuStockLockServiceImpl implements SkuStockLockService {

    private final StockBillLogService stockBillLogService;
    private final StockManager stockManager;;
    private final SkuService skuService;
    private final SkuStockService skuStockService;
    private final ApplicationEventPublisher eventPublisher;
    private final ProductService productService;
    private final StockPointSkuService stockPointSkuService;
    private final WarehouseService warehouseService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ServerResponseEntity<?> lock(List<SkuStockVO> skuStockList, List<ShopCartOrderDto> shopCartOrders) {
        // 锁定的sku库存
        String stockInfo = this.lockStock(skuStockList);
        if (!stockInfo.contains(StrUtil.COMMA)) {
            log.info("lock sku stock data: {}", stockInfo);
            Long id = Long.valueOf(stockInfo);
            if (id < 0) {
                // 提交订单时，商品已下架
                throw new YamiShopBindException("yami.product.disable");
            }
            return ServerResponseEntity.fail(ResponseEnum.NOT_STOCK, stockInfo);
        }
        // 处理库存为0的关联数据
        handSkuStockPoint(stockInfo);
        Map<String, SkuStockVO> orderResultMap = StockUtil.getSkuResultMap(stockInfo);
        for (ShopCartOrderDto shopCartOrder : shopCartOrders) {
            for (ShopCartItemDiscountDto shopCartItemDiscount : shopCartOrder.getShopCartItemDiscounts()) {
                for (ShopCartItemDto shopCartItem : shopCartItemDiscount.getShopCartItems()) {
                    if (orderResultMap.containsKey(shopCartItem.getSkuId() + Constant.UNDERLINE + shopCartItem.getStockPointId())) {
                        SkuStockVO skuStockVO = orderResultMap.get(shopCartItem.getSkuId() + Constant.UNDERLINE + shopCartItem.getStockPointId());
                        shopCartItem.setAfterStock(skuStockVO.getStock());
                    }
                }
            }
        }

        // 生成出库明细
        stockBillLogService.generateStockLogOfShopCartOrderList(
                shopCartOrders, Collections.emptyMap(),
                StockBillType.PURCHASE_OUTBOUND.value(), StockType.OUT_OF_STOCK.value());
        return ServerResponseEntity.success();
    }

    @Override
    public String lockStock(List<SkuStockVO> skuStockList) {
        return stockManager.lockStock(skuStockList);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void unlockStock(List<Order> orders) {
        Set<Long> prodIds = new HashSet<>(Constant.INITIAL_CAPACITY);
        Set<Long> skuIds = new HashSet<>(Constant.INITIAL_CAPACITY);
        List<SkuStockVO> skuStockList = new ArrayList<>(orders.size());
        for (Order order : orders) {
            for (OrderItem orderItem : order.getOrderItems()) {
                prodIds.add(orderItem.getProdId());
                skuIds.add(orderItem.getSkuId());
                // 解锁库存数量不用填，lua中有处理，根据订单记录中锁定的数量来解锁
                SkuStockVO skuStockVO = new SkuStockVO(order.getOrderNumber(), orderItem.getSkuId(), 0, orderItem.getStockPointId());
                skuStockVO.setProdId(orderItem.getProdId());
                skuStockVO.setShopId(order.getShopId());
                if (Objects.equals(orderItem.getMold(), ProdMoldEnum.COMBO.value())) {
                    skuStockVO.setStockPointId(0L);
                }
                skuStockList.add(skuStockVO);
            }
        }

        // TODO stock
        Map<Long, Long> skuMap = resetStock(skuStockList);
        // 实际解锁库存操作
        Map<String, SkuStockVO> stockResultMap = stockManager.unLockStock(skuStockList);

        List<Order> list;
        List<Sku> deleteSkus = skuService.list(new LambdaQueryWrapper<Sku>().select(Sku::getSkuId).in(Sku::getSkuId, skuIds).eq(Sku::getIsDelete, 1));
        List<Long> deleteSkuIds = deleteSkus.stream().map(Sku::getSkuId).collect(Collectors.toList());

        // 移除sku已经删除的订单项 - sku删除后不用添加库存记录
        if (CollUtil.isNotEmpty(deleteSkuIds)) {
            list = new ArrayList<>(orders);
            list.removeIf(order -> {
                order.getOrderItems().removeIf(orderItem -> deleteSkuIds.contains(orderItem.getSkuId()));
                //订单项全部删除的订单也要删除
                if (CollUtil.isEmpty(order.getOrderItems())) {
                    return Boolean.TRUE;
                }
                return Boolean.FALSE;
            });
        }

        // 替换订单中已经被删除的仓库id
        Map<String, Long> skuPointKeyMap = skuStockList.stream().filter(skuStockVO -> Objects.nonNull(skuStockVO.getDefaultPointId()))
                .collect(Collectors.toMap(skuStockVO -> skuStockVO.getOrderNumber() + Constant.UNDERLINE + skuStockVO.getSkuId(), SkuStockVO::getDefaultPointId, (v1, v2) -> v1));
        if (!skuPointKeyMap.isEmpty()) {
            for (Order order : orders) {
                for (OrderItem orderItem : order.getOrderItems()) {
                    if (Objects.equals(orderItem.getMold(), ProdMoldEnum.COMBO.value())) {
                        continue;
                    }
                    // 普通订单项
                    Long skuStockPointId = skuPointKeyMap.get(orderItem.getOrderNumber() + Constant.UNDERLINE + orderItem.getSkuId());
                    if (Objects.nonNull(skuStockPointId) && !Objects.equals(orderItem.getStockPointId(), skuStockPointId)) {
                        orderItem.setStockPointId(skuStockPointId);
                        orderItem.setStockPointType(StockPointType.WAREHOUSE.getValue());
                    }
                }
            }
        }
        for (Order order : orders) {
            for (OrderItem orderItem : order.getOrderItems()) {
                String key = orderItem.getOrderNumber() + Constant.UNDERLINE + orderItem.getSkuId();
                if (stockResultMap.containsKey(key)) {
                    SkuStockVO skuStockVO = stockResultMap.get(key);
                    orderItem.setAfterStock(skuStockVO.getStock());
                }
            }
        }
        // 生成出入库明细信息
        stockBillLogService.generateStockLogOfOrderList(orders, StockBillType.ORDER_CANCEL.value(), StockType.WAREHOUSING.value());
        // 更改库存关联数据后，要清除对应sku的缓存
        skuService.batchRemoveSkuStockCache(skuMap);
        // 更新es中的商品库存
        try {
            eventPublisher.publishEvent(new EsProductUpdateEvent(null, new ArrayList<>(prodIds), EsOperationType.UPDATE_SOLD_NUM_BATCH));
        } catch (Exception e) {
            log.error("取消订单-更新es商品库存失败，错误为：", e);
        }
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void markerStockUse(List<OrderItem> orderItems) {
        List<SkuStockVO> stockList = new ArrayList<>();
        for (OrderItem orderItem : orderItems) {
            SkuStockVO skuStockLock = new SkuStockVO(orderItem.getOrderNumber(), orderItem.getSkuId(), orderItem.getProdCount(), orderItem.getStockPointId());
            if (Objects.equals(orderItem.getMold(), ProdMoldEnum.COMBO.value())) {
                skuStockLock.setStockPointId(Constant.ZERO_LONG);
            }
            stockList.add(skuStockLock);
        }
        stockManager.markerUseStock(stockList);
        //  ==============订单从正常状态变成已支付=============
        Set<Long> prodIds = new HashSet<>(Constant.INITIAL_CAPACITY);
        for (OrderItem orderItem : orderItems) {
            // 移除缓存
            productService.removeProdCacheByProdId(orderItem.getProdId());
            skuService.removeSkuCacheBySkuId(orderItem.getSkuId(), orderItem.getProdId());
            prodIds.add(orderItem.getProdId());
        }
        // 更新ES数据
        EsProductUpdateEvent event = new EsProductUpdateEvent(null, new ArrayList<>(prodIds), EsOperationType.UPDATE_BATCH);
        eventPublisher.publishEvent(event);
    }

    @Override
    public String lockAllotOrder(List<SkuStockVO> skuStockList) {
        List<String[]> stockList = this.listStringArrayBySkuStockLocks(skuStockList);
        // 扣除库存，同时添加锁定记录
        return stockManager.executeLuk(stockList, StockManager.ALLOT_LOCK_STOCK_BYTES);
    }

    @Override
    public String reduceLockAllotOrder(List<SkuStockVO> skuStockList) {
        List<String[]> stockList = this.listStringArrayBySkuStockLocks(skuStockList);
        // 减少锁定库存，同时删除锁定记录
        return stockManager.executeLuk(stockList, StockManager.ALLOT_REDUCE_LOCK_STOCK_BYTES);
    }

    @Override
    public void unLockStock(List<SkuStockVO> skuStockList) {
        List<Long> skuIds = skuStockList.stream().map(SkuStockVO::getSkuId).toList();
        List<Sku> skus = skuService.listByIds(skuIds);
        List<Long> prodIds = skus.stream().map(Sku::getProdId).toList();
        List<Product> products = productService.listByIds(prodIds);
        Map<Long, Long> skuMap = skus.stream().collect(Collectors.toMap(Sku::getSkuId, Sku::getProdId));
        Map<Long, Long> prodMap = products.stream().collect(Collectors.toMap(Product::getProdId, Product::getShopId));
        for (SkuStockVO skuStockVO : skuStockList) {
            skuStockVO.setProdId(skuMap.get(skuStockVO.getSkuId()));
            skuStockVO.setShopId(prodMap.get(skuStockVO.getProdId()));
        }
        Map<Long, Long> skuKeyMap = resetStock(skuStockList);
        stockManager.unLockStock(skuStockList);
        skuService.batchRemoveSkuStockCache(skuKeyMap);
    }

    private List<String[]> listStringArrayBySkuStockLocks(List<SkuStockVO> skuStockList) {
        List<String[]> stockList = new ArrayList<>(skuStockList.size());
        for (SkuStockVO skuStockVO : skuStockList) {
            stockList.add(StockUtil.loadQueryArray(
                    skuStockVO.getOrderNumber(),
                    Objects.isNull(skuStockVO.getStockPointId()) ? StockManager.WAREHOUSE_ID : skuStockVO.getStockPointId(),
                    skuStockVO.getSkuId(),
                    skuStockVO.getStock()
            ));
        }
        return stockList;
    }

    /**
     * 处理一下锁定库存后的关联库存数据，为0就删除关联数据
     * @param stockInfo
     */
    private void handSkuStockPoint(String stockInfo) {
        String[] splitList = stockInfo.split(",");
        List<Long> defaultIdList = warehouseService.listDefaultWarehouses().stream().map(WarehouseVO::getWarehouseId).collect(Collectors.toList());
        for (String split : splitList) {
            String[] stockSplit = split.split("_");
            if (stockSplit[1] == "0") {
                continue;
            }
            Long pointId = Long.parseLong(stockSplit[1]);
            Long skuId = Long.parseLong(stockSplit[2]);
            int stock = Integer.parseInt(stockSplit[stockSplit.length-1]);
            if (stock == 0 && !defaultIdList.contains(pointId)) {
                stockPointSkuService.removeBySkuIdAndPointId(skuId, pointId);
            }
        }
    }

    private Map<Long, Long> resetStock(List<SkuStockVO> skuStockList) {
        return skuStockService.resetStock(skuStockList);
    }
}
